package cn.victor.service.impl.dubbo.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Activate(group = {CommonConstants.CONSUMER})
public class TPMonitorFilter implements Filter {
    private Map<String,CallRecord> costMap = new ConcurrentHashMap<>();

    public TPMonitorFilter(){
        new Thread(()->{

            int i=0;
            while(true){
                try {
                    Thread.sleep(5000L);
                    System.out.println("["+(++i)+"]--------------------------");
                    costMap.values().forEach(record->{
                        record.keepOneMinuteRecord();
                        record.printTP90();
                        record.printTP99();
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }).start();
    }
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        long startTime = System.currentTimeMillis();
        Result result = invoker.invoke(invocation);
        long endTime = System.currentTimeMillis();
        String key = invocation.getServiceName()+"#"+invocation.getMethodName();
        if(!costMap.containsKey(key)){
            costMap.put(key,new CallRecord(invocation.getServiceName(),invocation.getMethodName()));
        }
        costMap.get(key).addCostTime(startTime,endTime);
        return result;
    }
    public static class CallRecord{
        //调用服务名
        private String service;
        //调用方法名
        private String method;

        private List<CallCost> callCostList = new LinkedList<>();

        public CallRecord(String service, String method) {
            this.service = service;
            this.method = method;
        }
        /**
         * 添加方法的耗时记录
         * @param startTime
         * @param endTime
         * @return
         */
        public synchronized CallRecord addCostTime(long startTime,long endTime){
            CallCost cost = new CallCost(startTime,endTime-startTime);
            callCostList.add(cost);
            return this;
        }

        public String getService() {
            return service;
        }

        public String getMethod() {
            return method;
        }

        /**
         * 只保留一分钟的记录
         */
        public synchronized void keepOneMinuteRecord(){
            long lastTime = System.currentTimeMillis()-60000;
            Iterator<CallCost> iterator = callCostList.iterator();
            while (iterator.hasNext()){
                CallCost cost = iterator.next();
                if(cost.startTime<lastTime){
                    iterator.remove();
                }
            }
        }

        public synchronized void printTP90(){
            //1、拷贝一份记录，对记录按耗时大小进行降序排序
            List<CallCost> tmpList = getSortedCostList();
            //2、TP90就是 当前记录数*90% 的记录数中耗时最长的时间，也就是下标为 记录数*10% 的记录的耗时
            int index = tmpList.size()/10;
            if(tmpList.size()>0){
                System.out.println("["+service+"]["+method+"][tp90]:"+tmpList.get(index).getCostTime() +"ms");
            }
        }
        public synchronized void printTP99(){
            List<CallCost> tmpList = getSortedCostList();
            int index = tmpList.size()/100;
            if(tmpList.size()>0){
                System.out.println("["+service+"]["+method+"][tp99]:"+tmpList.get(index).getCostTime() +"ms");
            }
        }

        /**
         * 获取按耗时降序
         * @return
         */
        public List<CallCost> getSortedCostList(){
            List<CallCost> tmpList = new ArrayList<>();
            callCostList.forEach(cost->tmpList.add(cost));
            tmpList.sort(new Comparator<CallCost>() {
                @Override
                public int compare(CallCost o1, CallCost o2) {
                    if(o1.getCostTime()<o2.getCostTime()){
                        return 1;
                    }else if(o1.getCostTime()==o2.getCostTime()){
                        return 0;
                    }else{
                        return -1;
                    }
                }
            });
            return tmpList;
        }
    }
    public static class CallCost{
        private long startTime;
        private long costTime;

        public CallCost(long startTime, long costTime) {
            this.startTime = startTime;
            this.costTime = costTime;
        }

        public long getStartTime() {
            return startTime;
        }

        public long getCostTime() {
            return costTime;
        }
    }
}
